﻿using ImageControl.Common;
using System;
using System.Windows;
using System.Windows.Media;
using System.Collections.ObjectModel;
using ImageControl.Models;
using ImageControl.ViewModels.Handlers;
using ImageControl.Resources;
using System.Windows.Media.Imaging;

namespace ImageControl.ViewModels
{
    public class AdornerViewModel : BindableBase, ICapturable
    {
        private const int HandlersRowColumnCount = 3;
        private const int HandlersCount = HandlersRowColumnCount * HandlersRowColumnCount;
        static readonly SolidColorBrush ReleaseColor = new SolidColorBrush(Colors.Transparent);
        static readonly SolidColorBrush HoveredColor = new SolidColorBrush(Colors.Red)
            {
                Opacity = 0.2
            };
        static readonly SolidColorBrush CapturedColor = new SolidColorBrush(Colors.Yellow)
        {
            Opacity = 0.2
        };

        private Point lastCapturedPoint = new Point();
        private ImageModel imageModel;
        private SolidColorBrush background;
        private BitmapImage image;

        public BitmapImage Image
        {
            get
            {
                return this.image;
            }
            set
            {
                this.SetProperty(ref this.image, value, "Image");
            }
        }

        public SolidColorBrush Background
        {
            get
            {
                return this.background;
            }
            set
            {
                this.SetProperty(ref this.background, value, "Background");
            }
        }

        public ImageModel ImageModel
        {
            get
            {
                return this.imageModel;
            }
            set
            {
                if (this.SetProperty(ref this.imageModel, value, "ImageModel"))
                {
                    this.UpdateHandlersPositioning();
                }
            }
        }       

        public ObservableCollection<HandlerViewModel> Handlers { get; private set; }

        private Matrix RotatedToDocumentTransformation
        {
            get
            {
                Matrix m = new Matrix();
                m.Rotate(this.ImageModel.Rotation);
                m.Translate(this.ImageModel.RotatedTopLeft.X, this.ImageModel.RotatedTopLeft.Y);

                return m;
            }
        }

        private Matrix DocumentToRotatedTransformation
        {
            get
            {
                Matrix m = this.RotatedToDocumentTransformation;
                m.Invert();

                return m;
            }
        }

        private Matrix LocalToDocumentTransformation
        {
            get
            {
                Matrix m = new Matrix();
                m.Translate(this.ImageModel.ImageTopLeft.X, this.ImageModel.ImageTopLeft.Y);

                return m;
            }
        }
        
        private Matrix DocumentToLocalTransformation
        {
            get
            {
                Matrix m = this.LocalToDocumentTransformation;
                m.Invert();

                return m;
            }
        }

        public AdornerViewModel(string resourceString, double orientedWidth, double orientedHeight, double rotation, Point imageTopLeft)
        {
            Uri uriSource = ResourceHelper.GetResourceUri(resourceString);
            this.Image = new BitmapImage(uriSource);

            this.Handlers = new ObservableCollection<HandlerViewModel>();
            this.Handlers.Add(new TopLeftHandler(this));
            this.Handlers.Add(new TopCenterHandler(this));
            this.Handlers.Add(new TopRightHandler(this));
            this.Handlers.Add(new LeftCenterHandler(this));
            this.Handlers.Add(new RotateHandler(this));
            this.Handlers.Add(new RightCenterHandler(this));
            this.Handlers.Add(new BottomLeftHandler(this));
            this.Handlers.Add(new BottomCenterHandler(this));
            this.Handlers.Add(new BottomRightHandler(this));

            this.ImageModel = new ImageModel(orientedWidth, orientedHeight, rotation, imageTopLeft);

            this.ReleaseHover();
        }

        private static int GetHandlerIndex(int i, int j)
        {
            return i * HandlersRowColumnCount + j;
        }

        private void UpdateHandlersPositioning()
        {
            double handlerSize = HandlerViewModel.StandartHandlerSize;

            double leftHanging = this.ImageModel.HasFlippingX ? 0 : handlerSize;
            double rightHanging = this.ImageModel.HasFlippingX ? handlerSize : 0;

            double[] xOffsets = 
            {
                0 - leftHanging,
                this.ImageModel.OrientedWidth / 2 - HandlerViewModel.StandartHandlerSize / 2,
                this.ImageModel.OrientedWidth - rightHanging
            };

            double topHanging = this.ImageModel.HasFlippingY ? 0 : handlerSize;
            double bottomHanging = this.ImageModel.HasFlippingY ? handlerSize : 0;

            double[] yOffsets = 
            {
                0 - topHanging,
                this.ImageModel.OrientedHeight / 2 - HandlerViewModel.StandartHandlerSize / 2,
                this.ImageModel.OrientedHeight - bottomHanging
            };

            for (int row = 0; row < HandlersRowColumnCount; row++)
            {
                for (int column = 0; column < HandlersRowColumnCount; column++)
                {
                    int index = GetHandlerIndex(row, column);
                    HandlerViewModel handler = this.Handlers[index];
                    double x = xOffsets[column];
                    double y = yOffsets[row];
                    handler.TopLeft = new Point(x, y);
                }
            }

            double rotateHanging = this.ImageModel.HasFlippingY ? 2 * handlerSize : -2 * handlerSize; 

            int rotateHandlerIndex = GetHandlerIndex(1, 1);
            this.Handlers[rotateHandlerIndex].TopLeft = 
                this.Handlers[rotateHandlerIndex].TopLeft
                .Minus(new Point(0, this.ImageModel.OrientedHeight / 2 - rotateHanging));
        }

        internal Point GetLocalCoordinateFromDocumentPoint(Point documentPoint)
        {
            return this.DocumentToLocalTransformation.Transform(documentPoint);
        }

        internal Point GetDocumentCoordinateFromLocalPoint(Point localPoint)
        {
            return this.LocalToDocumentTransformation.Transform(localPoint);
        }

        internal Point GetRotatedCoordinateFromDocumentPoint(Point documentPoint)
        {
            return this.DocumentToRotatedTransformation.Transform(documentPoint);
        }

        internal Point GetDocumentCoordinateFromRotatedPoint(Point rotatedPoint)
        {
            return this.RotatedToDocumentTransformation.Transform(rotatedPoint);
        }

        public void Hover()
        {
            this.Background = AdornerViewModel.HoveredColor;
        }

        public bool ShouldHandle(Point coordinate)
        {
            double minWidth = Math.Min(0, this.ImageModel.OrientedWidth);
            double maxWidth = Math.Max(0, this.ImageModel.OrientedWidth);

            double minHeight = Math.Min(0, this.ImageModel.OrientedHeight);
            double maxHeight = Math.Max(0, this.ImageModel.OrientedHeight);

            return
                coordinate.X.IsBetween(minWidth, maxWidth) &&
                coordinate.Y.IsBetween(minHeight, maxHeight);
        }

        public void ReleaseHover()
        {
            this.Background = AdornerViewModel.ReleaseColor;
        }

        public bool TryGetHoveredHandler(Point documentPoint, out IHoverable hoveredHandler)
        {
            hoveredHandler = null;
            Point rotatedCoordinate = GetRotatedCoordinateFromDocumentPoint(documentPoint);

            for (int i = 0; i < HandlersCount; i++)
            {
                if(this.Handlers[i].ShouldHandle(rotatedCoordinate))
                {
                    hoveredHandler = this.Handlers[i];
                    return true;
                }
            }

            return false;
        }

        public void Capture(Point coordinate)
        {
            this.Background = AdornerViewModel.CapturedColor;
            this.lastCapturedPoint = this.GetRotatedCoordinateFromDocumentPoint(coordinate);
        }

        public void HandleMove(Point coordinate)
        {
            Point lastCapturedDocumentPosition = this.GetDocumentCoordinateFromRotatedPoint(this.lastCapturedPoint);
            Point translation = coordinate.Minus(lastCapturedDocumentPosition);
            Point topLeft = this.ImageModel.ImageTopLeft.Plus(translation);

            this.ImageModel = new ImageModel(this.ImageModel.OrientedWidth, this.ImageModel.OrientedHeight, this.ImageModel.Rotation, topLeft);
        }

        public void ReleaseCapture()
        {
            this.Background = AdornerViewModel.ReleaseColor;
        }
    }
}
